package com.tvunetworks.center.device.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.type.TypeReference;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.tvunetworks.center.common.constant.LocalConstant;
import com.tvunetworks.center.common.util.MyUtil;
import com.tvunetworks.center.device.mapper.DeviceGeoMapper;
import com.tvunetworks.center.device.mapper.MyDeviceMapper;
import com.tvunetworks.center.device.model.DeviceGeo;
import com.tvunetworks.center.device.model.vo.GeoFromGeoService;
import com.tvunetworks.center.device.service.DeviceGeoService;
import lombok.extern.slf4j.Slf4j;
import net.spy.memcached.MemcachedClient;
import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @program: tvucc-aggregator
 * @description: DeviceGeoServiceImpl
 * @author lebronchen
 * @create: 2020-04-20 15:21
 **/
@Service
@Slf4j
public class DeviceGeoServiceImpl extends BaseService<DeviceGeo> implements DeviceGeoService {

    @Autowired
    private MyDeviceMapper myDeviceMapper;
    @Autowired
    private DeviceGeoMapper deviceGeoMapper;
    @Autowired
    @Qualifier("psMemcacheClient")
    private MemcachedClient psMemcacheClient;
    @Autowired
    @Qualifier("ccMemcacheClient")
    private MemcachedClient ccMemcacheClient;
    @Autowired
    private RestTemplate restTemplate;
    @Resource(name = "redisTpdsTemplate")
    private RedisTemplate tpdsRedisTemplate;

    @Value("${tpds.getAllTFlag}")
    private boolean getAllTFlag;
    @Value("${tpds.allDevice.redis.flag:true}")
    private boolean allDeviceRedisFlag;
    @Value("${tpds.allTIDSize}")
    private String allTIDSize;
    @Value("${tpds.allTID}")
    private String allTID;
    @Value("${onlinePeerIdSize}")
    private int onlinePeerIdSize;

    @Value("${tpds.getAllRFlag}")
    private boolean getAllRFlag;
    @Value("${tpds.allRIDSize}")
    private String allRIDSize;
    @Value("${tpds.allRID}")
    private String allRID;
    @Value("${geoService.url.getGPSBypeeridAndTimestampServlet}")
    private String getGPSBypeeridAndTimestampServlet;
    @Value("${initDeviceGeo.pageSize}")
    private int pageSize;
    @Value("${initDeviceGeo.step.sleepTime:200}")
    private Long initDeviceGeoStepSleepTime;
    @Value("${initDeviceGeo.useNew:true}")
    private boolean initDeviceGeoUseNew;
    /**
     * DEBUG_WEBSOCKET_RID_KEY
     */
    public static final String DEBUG_WEBSOCKET_RID_KEY = "debug_websocket_rid";

    @Override
    @Transactional
    public void initDeviceGeo() {
        log.error("initDeviceGeo start ********************* ");
        if (initDeviceGeoUseNew) {
            initDeviceGeoNew();
        } else {
            initDeviceGeoOld();
        }
        log.error("initDeviceGeo end ********************* ");

    }

    @Override
    public List<String> getAllTIDByRedis() {
        List<String> result = new ArrayList<>();
        if (getAllTFlag) {
            Integer size = (Integer) tpdsRedisTemplate.opsForValue().get(allTIDSize);
            if (size == null || size == 0) {
                log.error("getAllTIDByRedis failed getAllTFlag is true but memcache allTIDSize is wrong size:{}", size);
            } else {
                for (int i = 0; i < size; i++) {
                    List<String> temp = (List<String>) tpdsRedisTemplate.opsForValue().get(allTID + "_" + i);
                    if (!CollectionUtils.isEmpty(temp)) {
                        result.addAll(temp);
                    }
                }
            }
        }else {
            result = (List<String>) tpdsRedisTemplate.opsForValue().get(allTID);
        }
        return result;
    }

    @Override
    public List<String> getAllRIDByRedis() {
        List<String> result = new ArrayList<>();
        if (getAllRFlag) {
            Integer size = (Integer) tpdsRedisTemplate.opsForValue().get(allRIDSize);
            if (size == null || size == 0) {
                log.error("getAllRIDByRedis failed getAllTFlag is true but memcache allRIDSize is wrong size:{}", size);
            } else {
                for (int i = 0; i < size; i++) {
                    List<String> temp = (List<String>) tpdsRedisTemplate.opsForValue().get(allRID + "_" + i);
                    if (!CollectionUtils.isEmpty(temp)) {
                        result.addAll(temp);
                    }
                }
            }
        }else {
            result = (List<String>) tpdsRedisTemplate.opsForValue().get(allRID);
        }
        return result;
    }

    private void initDeviceGeoNew() {
        log.error("initDeviceGeo start");
        // 把 TPDS 中 online T/R 拉到内存
        List<String> onlinePeerId = initOnlineTR();
        if (CollectionUtils.isEmpty(onlinePeerId)) {
            return;
        }

        List<List<String>> peerIdList = MyUtil.stepPage(onlinePeerId, pageSize);
        for (List<String> list : peerIdList) {
            try {
                this.saveAndUpdateDeviceGeo(list);
            } catch (Exception e) {
                log.error(e.toString(), e);
            }

            try {
                Thread.sleep(initDeviceGeoStepSleepTime);
            } catch (InterruptedException e) {
                log.error(e.toString(), e);
            }
        }
        log.error("initDeviceGeo end");

    }

    private void initDeviceGeoOld() {
        log.error("initDeviceGeo start");
        // 把 TPDS 中 online T/R 拉到内存
        List<String> onlinePeerId = initOnlineTR();

        int pageNum = 0;

        boolean hasNextPage = true;
        while (hasNextPage) {
            PageInfo<String> pageInfo = pagePeerIdByTypes(pageNum, pageSize);
            List<String> list = pageInfo.getList();
            try {
                this.saveAndUpdateDeviceGeoOld(list, onlinePeerId);
                Thread.sleep(5000);
            } catch (Exception e) {
                log.error(e.toString(), e);
            }

            pageNum ++;
            hasNextPage = pageInfo.isHasNextPage();
        }
        log.error("initDeviceGeo end");

    }

    private PageInfo<String> pagePeerIdByTypes(int pageNum, int pageSize){
        PageHelper.startPage(pageNum, pageSize);
        List<String> list = myDeviceMapper.listPeerIdByTypes(Arrays.asList(new String[]{LocalConstant.DeviceType.R, LocalConstant.DeviceType.T, LocalConstant.DeviceType.X}));
        return new PageInfo<>(list);
    }

    /**
     *
     * @param list list
     * @param onlinePeerId \onlinePeerId
     */
    @Transactional
    public void saveAndUpdateDeviceGeoOld(List<String> list, List<String> onlinePeerId) {
        if (CollectionUtils.isEmpty(list)) {
            log.error("saveAndUpdateDeviceGeo list is null");
            return;
        }
        String debugId = (String)ccMemcacheClient.get(DEBUG_WEBSOCKET_RID_KEY);
        // 过滤出在线的设备，去 GeoService 查询位置
        List<String> peerIdList = new ArrayList<>(list);
        peerIdList.retainAll(onlinePeerId);
        if(!StringUtils.isEmpty(debugId)){
            if(JSONObject.toJSONString(list).contains(debugId)) {
                log.error("saveAndUpdateDeviceGeo PeerId  ===peerIdList==="+peerIdList+"===list===list==" + JSONObject.toJSONString(list) + "===onlinePeerId===" + JSONObject.toJSONString(onlinePeerId));
            }
        }

        // peerId -> GeoFromGeoService

        Map<String, GeoFromGeoService> deviceGeoMap = null;
        if (!CollectionUtils.isEmpty(peerIdList)) {
            try {
                List<GeoFromGeoService> geos = this.getDeviceGeoFromGeoService(peerIdList);
                if(!StringUtils.isEmpty(debugId)) {
                    if (JSONObject.toJSONString(geos).toLowerCase().contains(debugId)) {
                        log.error("List<GeoFromGeoService> geos===" + JSONObject.toJSONString(geos));
                    }
                }
                if (geos != null) {
                    deviceGeoMap = geos.stream().collect(Collectors.toMap(item -> item.getPeerID().toLowerCase(), item -> item));
                }
            } catch (IOException e) {
                log.error(e.toString(), e);
            }
        }

        if (deviceGeoMap == null) {
            deviceGeoMap = new HashedMap();
        }

        // 查询数据库中的 DeviceGeo
        List<DeviceGeo> dbList = deviceGeoMapper.getDevicePositionByPeerIds(list);
        Map<String, DeviceGeo> dbMap = dbList.stream().collect(Collectors.toMap(item -> item.getPeerId().toLowerCase(), item -> item));


        List<DeviceGeo> needUpdateList = new ArrayList<>();
        List<DeviceGeo> needAddList = new ArrayList<>();

        for (String peerId : list) {
            String geoPeerId = peerId.length() == 32 ? peerId.substring(0, 16) : peerId;
            if(!StringUtils.isEmpty(debugId)) {
                if (peerId.toLowerCase().contains(debugId)) {
                    log.error("SAVE GEO  deviceGeoMap==" + JSONObject.toJSONString(deviceGeoMap));
                    log.error("SAVE GEO =dbMap=" + JSONObject.toJSONString(dbMap.get(peerId)) + "==deviceGeoMap==" + deviceGeoMap.get(geoPeerId.toLowerCase()));
                }
            }


            DeviceGeo dbExist = dbMap.get(peerId.toLowerCase());
            if (dbExist != null) {
                // DB 存在 update
                GeoFromGeoService geo = deviceGeoMap.get(geoPeerId.toLowerCase());
                if (geo == null) {
                    // 设备 Geo 没有查询到，不做修改
                    continue;
                }
                DecimalFormat df = new DecimalFormat("#.00000");
                String automatic = df.format(geo.getLatitude()) + "," + df.format(geo.getLongitude());
                dbExist.setAutomatic(automatic);
                if (dbExist.getStatus() == 3) {
                    dbExist.setStatus(1);
                }
                dbExist.setUpdateTime(System.currentTimeMillis());
                needUpdateList.add(dbExist);
            } else {
                // DB 不存在 add
                GeoFromGeoService geo = deviceGeoMap.get(geoPeerId);
                if (geo == null) {
                    // 设备 Geo 没有查询到，设置为 NO_POSITION
                    dbExist = new DeviceGeo()
                            .setPeerId(peerId)
                            .setAutomatic("0,0")
                            .setManual("0,0")
                            .setStatus(3);
                } else {
                    // 设备 Geo 查询到，设置为 AUTOMATIC
                    dbExist = new DeviceGeo()
                            .setPeerId(peerId)
                            .setAutomatic(geo.getLatitude() + "," + geo.getLongitude())
                            .setManual("0,0")
                            .setStatus(1);
                }
                needAddList.add(dbExist);
            }

        }
        if (needUpdateList.size() > 0) {//update 只更新automatic和status,所以做优化
            log.error("saveAndUpdateDeviceGeo update size:{}", needUpdateList.size());
            Long startTime = System.currentTimeMillis();
            deviceGeoMapper.batchUpdateGeoInfo(needUpdateList);//执行批量更新
            log.error("saveAndUpdateDeviceGeo ExcuteTime:"+(System.currentTimeMillis()-startTime));
//            log.error("saveAndUpdateDeviceGeo update list:{}", JSONObject.toJSONString(needUpdateList));
//            for (DeviceGeo deviceGeo : needUpdateList) {
//                if (deviceGeo.getPeerId().contains(debugId)){
//                    log.error("saveAndUpdateDeviceGeo update list:{}", JSONObject.toJSONString(deviceGeo));
//                }
//                    super.update(deviceGeo);
//
//            }
        }
        if (needAddList.size() > 0) {
            log.error("saveAndUpdateDeviceGeo insert size:{}", needAddList.size());
//            log.error("saveAndUpdateDeviceGeo update list:{}", JSONObject.toJSONString(needAddList));
            for (DeviceGeo deviceGeo : needAddList) {
                if(!StringUtils.isEmpty(debugId)) {
                    if (deviceGeo.getPeerId().contains(debugId)) {
                        log.error("saveAndUpdateDeviceGeo save list:{}", JSONObject.toJSONString(deviceGeo));
                    }
                }
                super.insert(deviceGeo);
            }
        }
    }

    /**
     *
     * @param list list
     */
    @Transactional
    public void saveAndUpdateDeviceGeo(List<String> list) {
        if (CollectionUtils.isEmpty(list)) {
            log.error("saveAndUpdateDeviceGeo list is null");
            return;
        }
        String debugId = (String)ccMemcacheClient.get(DEBUG_WEBSOCKET_RID_KEY);
        // 过滤出在线的设备，去 GeoService 查询位置
        List<String> peerIdList = new ArrayList<>(list);
        if(!StringUtils.isEmpty(debugId) && list.contains(debugId)) {
            log.error("saveAndUpdateDeviceGeo PeerId  ===peerIdList==="+peerIdList+"===list===list==" + JSONObject.toJSONString(list) + "===onlinePeerId===" + JSONObject.toJSONString(list));
        }

        // peerId -> GeoFromGeoService

        Map<String, GeoFromGeoService> deviceGeoMap = null;
        if (!CollectionUtils.isEmpty(peerIdList)) {
            try {
                List<GeoFromGeoService> geos = this.getDeviceGeoFromGeoService(peerIdList);
                if(!StringUtils.isEmpty(debugId)) {
                    if (JSONObject.toJSONString(geos).toLowerCase().contains(debugId)) {
                        log.error("List<GeoFromGeoService> geos===" + JSONObject.toJSONString(geos));
                    }
                }
                if (geos != null) {
                    deviceGeoMap = geos.stream().collect(Collectors.toMap(item -> item.getPeerID().toLowerCase(), item -> item));
                }
            } catch (IOException e) {
                log.error(e.toString(), e);
            }
        }

        if (deviceGeoMap == null) {
            deviceGeoMap = new HashedMap();
        }

        // 查询数据库中的 DeviceGeo
        List<DeviceGeo> dbList = deviceGeoMapper.getDevicePositionByPeerIds(list);
        Map<String, DeviceGeo> dbMap = dbList.stream().collect(Collectors.toMap(item -> item.getPeerId().toLowerCase(), item -> item));


        List<DeviceGeo> needUpdateList = new ArrayList<>();
        List<DeviceGeo> needAddList = new ArrayList<>();

        for (String peerId : list) {
            String geoPeerId = peerId.length() == 32 ? peerId.substring(0, 16) : peerId;
            if(!StringUtils.isEmpty(debugId)) {
                if (peerId.toLowerCase().contains(debugId)) {
                    log.error("SAVE GEO  deviceGeoMap==" + JSONObject.toJSONString(deviceGeoMap));
                    log.error("SAVE GEO =dbMap=" + JSONObject.toJSONString(dbMap.get(peerId)) + "==deviceGeoMap==" + deviceGeoMap.get(geoPeerId.toLowerCase()));
                }
            }

            DeviceGeo dbExist = dbMap.get(peerId.toLowerCase());
            if (dbExist != null) {
                // DB 存在 update
                GeoFromGeoService geo = deviceGeoMap.get(geoPeerId.toLowerCase());
                if (geo == null) {
                    // 设备 Geo 没有查询到，不做修改
                    continue;
                }
                DecimalFormat df = new DecimalFormat("#.00000");
                String automatic = df.format(geo.getLatitude()) + "," + df.format(geo.getLongitude());
                dbExist.setAutomatic(automatic);
                if (dbExist.getStatus() == 3) {
                    dbExist.setStatus(1);
                }
                dbExist.setUpdateTime(System.currentTimeMillis());
                needUpdateList.add(dbExist);
            } else {
                // DB 不存在 add
                GeoFromGeoService geo = deviceGeoMap.get(geoPeerId);
                if (geo == null) {
                    // 设备 Geo 没有查询到，设置为 NO_POSITION
                    dbExist = new DeviceGeo()
                            .setPeerId(peerId)
                            .setAutomatic("0,0")
                            .setManual("0,0")
                            .setStatus(3);
                } else {
                    // 设备 Geo 查询到，设置为 AUTOMATIC
                    dbExist = new DeviceGeo()
                            .setPeerId(peerId)
                            .setAutomatic(geo.getLatitude() + "," + geo.getLongitude())
                            .setManual("0,0")
                            .setStatus(1);
                }
                needAddList.add(dbExist);
            }

        }
        if (needUpdateList.size() > 0) {//update 只更新automatic和status,所以做优化
            log.error("saveAndUpdateDeviceGeo update size:{}", needUpdateList.size());
            Long startTime = System.currentTimeMillis();
            deviceGeoMapper.batchUpdateGeoInfo(needUpdateList);//执行批量更新
            log.error("saveAndUpdateDeviceGeo ExcuteTime:"+(System.currentTimeMillis()-startTime));
//            log.error("saveAndUpdateDeviceGeo update list:{}", JSONObject.toJSONString(needUpdateList));
//            for (DeviceGeo deviceGeo : needUpdateList) {
//                if (deviceGeo.getPeerId().contains(debugId)){
//                    log.error("saveAndUpdateDeviceGeo update list:{}", JSONObject.toJSONString(deviceGeo));
//                }
//                    super.update(deviceGeo);
//
//            }
        }
        if (needAddList.size() > 0) {
            log.error("saveAndUpdateDeviceGeo insert size:{}", needAddList.size());
//            log.error("saveAndUpdateDeviceGeo update list:{}", JSONObject.toJSONString(needAddList));
            for (DeviceGeo deviceGeo : needAddList) {
                if(!StringUtils.isEmpty(debugId)) {
                    if (deviceGeo.getPeerId().contains(debugId)) {
                        log.error("saveAndUpdateDeviceGeo save list:{}", JSONObject.toJSONString(deviceGeo));
                    }
                }
                super.insert(deviceGeo);
            }
        }
    }


    private List<String> initOnlineTR() {
        List<String> onlinePeerId = new ArrayList<>(onlinePeerIdSize);
        if (getAllTFlag) {
            Integer size = allDeviceRedisFlag ? (Integer) tpdsRedisTemplate.opsForValue().get(allTIDSize) : (Integer) psMemcacheClient.get(allTIDSize);
            if (size == null || size == 0) {
                log.error("initOnlineTR failed getAllTFlag is true but memcache allTIDSize is wrong size:{}", size);
            } else {
                for (int i = 0; i < size; i++) {
                    List<String> tList = null;
                    if (allDeviceRedisFlag) {
                        tList = (List<String>) tpdsRedisTemplate.opsForValue().get(allTID + "_" + i);
                    }else {
                        String tListString = (String) psMemcacheClient.get(allTID + "_" + i);
                        if (tListString != null) {
                            tList = getListByMemcacheString(tListString);
                        }
                    }
                    if (CollectionUtils.isEmpty(tList)) {
                        continue;
                    }
                    onlinePeerId.addAll(tList);
                }
            }
        } else {
            List<String> tList = new ArrayList<>();
            if (allDeviceRedisFlag) {
                tList = (List<String>) tpdsRedisTemplate.opsForValue().get(allTID);
            }else {
                String tListString = (String) psMemcacheClient.get(allTID);
                if (tListString != null) {
                    tList = getListByMemcacheString(tListString);
                }
            }
            if (!CollectionUtils.isEmpty(tList)) {
                onlinePeerId.addAll(tList);
            }
        }

        if (getAllRFlag) {
            Integer size = allDeviceRedisFlag ? (Integer) tpdsRedisTemplate.opsForValue().get(allRIDSize) : (Integer) psMemcacheClient.get(allRIDSize);
            if (size == null || size == 0) {
                log.error("initOnlineTR failed getAllRFlag is true but memcache allRIDSize is wrong size:{}", size);
            } else {
                for (int i = 0; i < size; i++) {
                    List<String> rList = null;
                    if (allDeviceRedisFlag) {
                        rList = (List<String>) tpdsRedisTemplate.opsForValue().get(allRID + "_" + i);
                    }else {
                        String rListString = (String) psMemcacheClient.get(allRID + "_" + i);
                        if (rListString != null) {
                            rList = getListByMemcacheString(rListString);
                        }
                    }
                    if (CollectionUtils.isEmpty(rList)) {
                        continue;
                    }
                    onlinePeerId.addAll(rList);
                }
            }
        } else {
            List<String> rList = null;
            if (allDeviceRedisFlag) {
                rList = (List<String>) tpdsRedisTemplate.opsForValue().get(allRID);
            }else {
                String rListString = (String) psMemcacheClient.get(allRID);
                if (rListString != null) {
                    rList = getListByMemcacheString(rListString);

                }
            }
            if (!CollectionUtils.isEmpty(rList)) {
                onlinePeerId.addAll(rList);
            }
        }
        log.error("initOnlineTR initSize:{} size:{}", onlinePeerIdSize, onlinePeerId.size());
        return onlinePeerId;

    }

    private List<String> getListByMemcacheString(String string){
        if (StringUtils.isEmpty(string)) {
            return new ArrayList<>(0);
        }
        string = string.replace("[", "").replace("]", "").replace(" ", "");
        return Arrays.asList(string.split(","));
    }

    private List<GeoFromGeoService> getDeviceGeoFromGeoService(List<String> peerIdList) throws IOException {
        String debugId = (String)ccMemcacheClient.get(DEBUG_WEBSOCKET_RID_KEY);
        if (CollectionUtils.isEmpty(peerIdList)) {
            return null;
        }
        String url = getGPSBypeeridAndTimestampServlet;
        if (!StringUtils.startsWith(url, "http")) {
            url = "http://" + url;
        }

        String peerIds = StringUtils.join(peerIdList, "_");
      /*  Map<String, String> param = new HashMap<>();
        param.put("peerid", peerIds);*/

        url = url + "?peerid=" + peerIds;
        if(!StringUtils.isEmpty(debugId)) {
            if (JSONObject.toJSONString(peerIdList).toLowerCase().contains(debugId)) {
                log.error("URL" + url + "==getDeviceGeoFromGeoService==sssssss==before===peerIdList=" + JSONObject.toJSONString(peerIdList).toLowerCase());
            }
        }
        String body = restTemplate.postForObject(url, null, String.class);
        if(!StringUtils.isEmpty(debugId)) {
            if (JSONObject.toJSONString(peerIdList).toLowerCase().contains(debugId)) {
                log.error("URL" + url + "==getDeviceGeoFromGeoService==sssssss==after====" + body + "===peerIdList=" + JSONObject.toJSONString(peerIdList).toLowerCase());
            }
        }
        if (StringUtils.isEmpty(body)) {
            return null;
        }
        if (StringUtils.contains(body, "code")) {
            return null;
        }
//        JavaType javaType = MyUtil.getCollectionType(ArrayList.class, GeoFromGeoService.class);
//        MyUtil.MAPPER.readValue(body, javaType);
        List<GeoFromGeoService> list = null;
        try {
            list = MyUtil.MAPPER.readValue(body, new TypeReference<List<GeoFromGeoService>>(){});
        } catch (IOException e) {
            log.error("== MyUtil.MAPPER.readValue exception==",e);
        }
        if(!StringUtils.isEmpty(debugId)) {
            if (JSONObject.toJSONString(peerIdList).toLowerCase().contains(debugId)) {
                log.error("URL" + url + "==getDeviceGeoFromGeoService=======List<GeoFromGeoService>=" + JSONObject.toJSONString(list).toLowerCase());
            }
        }
//        List<GeoFromGeoService> list = JsonUtil.toObject(body, List.class);
        return list;
    }
}
